#pragma once

using namespace System;
using namespace System::Collections::Generic;

#include "Stimulus.h"
#include "PatternGenerator.h"
#include "PNGData.h"
#include "Connection.h"

namespace SpikingNeuronLib
{
	const double ForegroundStimulationLevelDefault = 40;			// Default level for foreground stimulation (more intense stimulation increases the probability of the external firing events firing at the specified time)
	const double BackgroundStimulationLevelDefault = 20;			// Default level for background stimulation

	const double PotentiationAmplitudeDefault = 0.1;				// STDP rule default for LTP window
	const double DepressionAmplitudeDefault = 0.12;					// STDP rule default for LTD window

	const double NetworkStateFileVersion = 1.0;						// File format version for network state files

	const double MetaplasticityDefaultInertia = 1.0;				// Default inertia value for scaling metaplasticity calculation
	const double MetaplasticityDefaultWeightLimitResistance = 20.0;	// Default resistance to change in synaptic weights near the limits
	const double MetaplasticityDefaultWeightLimitPrecision = 2.0;	// Default precision of synaptic weight scaling near the limits

	const int MaxSpikeInteractionDistance = 1000;			// Maximum separation (msec) between pre- and post-synaptic spikes for interaction to occur
	const unsigned char DefaultAssociationWindowSize = 8;	// window size (in msec) for associating a stimulation and subsequent firing event

	public delegate void OneSecondTickBeforeCleanupEventHandler(int, double, double, double);	// hooked before one second network cleanup operation
	public delegate void OneSecondTickAfterCleanupEventHandler(int);							// hooked after one second network cleanup operation
	public delegate void OneMillisecondTickEventHandler(int, int);								// hooked at the end of each millisecond
	public delegate void RunWarningEventHandler(String^);

	// handler args: string tag, int totalNumberOfGroups, int anchorNeuron, int firedNeurons, int maxLayer, int lastSpike, PNGData data
	// the tag arg allows the callback to be traced back to a specific invocation of FindPolychronousGroups*()
	// the PNGData arg just references the current state of the arrays
	// When needed call PNGData::GetPNG*() to convert to lists of FiringEvent and GroupLink (this allows conversion to be optional)
	public delegate void FoundPNGEventHandler(String^, int, int, int, int, int, PNGData^);			// called when a polychronous group has been found

	public value struct ArrayRef
	{
		int X;
		int Y;

		ArrayRef(int x, int y) { X = x; Y = y; }
	};

	static double C_max = 10.0f;

	public ref class NetworkBase abstract
	{
	protected:
		bool _initialized;

		// network params
		int _totalNeurons;					// the total number of neurons in the network
		int _numExcitatoryNeurons;			// the total number of excitatory neurons in the network
		int _numInhibitoryNeurons;			// the total number of inhibitory neurons in the network
		int _maxDelay;						// the maximum axonal delay in the network

		// params for firing stack
		int _N_firings;						// index of current top-of-stack
		int _N_firings_max;					// max size of stack (some implementations)

		// params for each neuron
		array<double>^ _a;
		array<double>^ _d;
		array<double>^ _v;
		array<double>^ _u;

		NetworkBase();

		// Abstract Methods -------------------------------------------------------------------------------------------

		virtual int RunImpl(int totalSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern) abstract;
		virtual int RunImpl(int startSeconds, int endSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern) abstract;

		// End of Abstract Methods -------------------------------------------------------------------------------------------

	public:
		// Events
		event OneSecondTickBeforeCleanupEventHandler^ OneSecondTickBeforeCleanup;
		event OneSecondTickAfterCleanupEventHandler^ OneSecondTickAfterCleanup;
		event OneMillisecondTickEventHandler^ OneMillisecondTick;
		event RunWarningEventHandler^ RunWarning;
		event FoundPNGEventHandler^ FoundPNG;

		// Properties
		property String^ AssemblyName
		{
		public:
			String^ get()
			{
				using namespace System::Reflection;
				Assembly^ assembly = Assembly::GetExecutingAssembly();
				return assembly->FullName;
			}
		}

		property int TotalNeurons
		{
		public:
			int get() { return _totalNeurons; }
		}

		property int ExcitatoryNeurons
		{
		public:
			int get() { return _numExcitatoryNeurons; }
		}

		property int InhibitoryNeurons
		{
		public:
			int get() { return _numInhibitoryNeurons; }
		}

		property int MaximumAxonalDelay
		{
		public:
			int get() { return _maxDelay; }
		}

		property array<double>^ MembranePotentials
		{
		public:
			array<double>^ get() { return _v; }
		}

		property array<double>^ MembraneRecoveryValues
		{
		public:
			array<double>^ get() { return _u; }
		}

		// Abstract Properties -------------------------------------------------------------------------------------------

		/// A list of all connections in the network
		property List<Connection^>^ Connections
		{
		public:
			virtual List<Connection^>^ get() abstract;
		}

		/// The number of synaptic connections made by each neuron
		/// This may return an average for non-grid networks
		property int ConnectionsPerNeuron
		{
		public:
			virtual int get() abstract;
		}

		property int TotalConnections
		{
		public:
			virtual int get() abstract;
		}

		property int ExcitatoryConnections
		{
		public:
			virtual int get() abstract;
		}

		property int InhibitoryConnections
		{
		public:
			virtual int get() abstract;
		}

		// End of Abstract Properties -------------------------------------------------------------------------------------------

		// Methods
		void Run(int totalSeconds, PatternGenerator^ backgroundPattern);		// a typical maturation run is 60*60*5 seconds
		void Run(int totalSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern);
		void Run(int startSeconds, int endSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern);

		// Abstract Methods -------------------------------------------------------------------------------------------

		// Firings data must be saved from within a OneSecondTickBeforeCleanup before the firing stack gets truncated
		virtual void SaveFiringsData(String^ filePath, bool includeOverflow) abstract;
		virtual void LoadFiringsData(String^ filePath) abstract;

		// Network state must NOT be saved from within a OneSecondTickBeforeCleanup event handler:
		// the cleanup code at the end of each second must be called first or the network state will be corrupt
		// Save the network state from within a OneSecondTickAfterCleanup event handler instead
		virtual void SaveNetworkState(String^ filePath) abstract;
		virtual void LoadNetworkState(String^ filePath) abstract;

		// This method must be called from within a OneSecondTickBeforeCleanup event handler
		// as code after this event callback cleans up the firing stack
		virtual array<FiringEvent>^ GetFiringsData(bool includeOverflow) abstract;

		// End of Abstract Methods -------------------------------------------------------------------------------------------

		// create a list of combinations of triplets of the input pattern
		static List<Triplet^>^ CreateTripletListFromInputPattern(Stimulus^ inputPattern);

		static array<array<int>^>^ CreateDelayMatrix(int M, int D, int Ne, int Ni);
	};
}